#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net.Sockets;

namespace Osh.ZoomPlayer
{
	public delegate void ReadLineEventHandler(object sender, string line);

	public class AsyncLineReader
	{
		private int StartBufferSize = 10000;
		private int BufferSizeIncrement = 1000;
		private int MaxBufferSize = 64000;
		private NetworkStream _stream;
		private byte[] _buffer;
		private int _bufferPosition;

		public AsyncLineReader(NetworkStream stream)
		{
			_stream = stream;
			_buffer = new byte[StartBufferSize];
			_bufferPosition = 0;
		}

		public void StartReading()
		{
			Allocate(1);
			_stream.BeginRead(_buffer, _bufferPosition, 1, new AsyncCallback(OnAsyncCallback), null);
		}

		private void OnAsyncCallback(IAsyncResult ar)
		{
			try
			{
				int read = _stream.EndRead(ar);
				if(read == 0)
				{
					CheckBufferForNewLines();
					return;
				}

				_bufferPosition += read;
				while(_stream.DataAvailable)
				{
					Allocate(BufferSizeIncrement);
					read = _stream.Read(_buffer, _bufferPosition, BufferSizeIncrement);
					_bufferPosition += read;
				}

				CheckBufferForNewLines();
				_stream.BeginRead(_buffer, _bufferPosition, 1, new AsyncCallback(OnAsyncCallback), null);
			}
			catch(Exception e)
			{
				Log.Exception(ZoomPlayerApplication.TraceSource, e, "ZoomPlayer read error.");
			}
		}

		private void Allocate(int space)
		{
			if(_bufferPosition + space <= _buffer.Length)
				return;
			
			if(space < BufferSizeIncrement)
				space = BufferSizeIncrement;

			int newBufferSize = _buffer.Length + space;
			if(newBufferSize > MaxBufferSize)
			{
				Log.Error(ZoomPlayerApplication.TraceSource, "ZoomPlayer reading buffer is overflowed.");
				_bufferPosition = 0;
				return;
			}

			Log.Info(ZoomPlayerApplication.TraceSource, "Extending ZoomPlayer reading buffer to {0} bytes.", newBufferSize);

			byte[] newBuffer = new byte[newBufferSize];
			Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _bufferPosition);
			_buffer = newBuffer;
		}

		private void CheckBufferForNewLines()
		{
			int lastNewLineIndex = 0;
			int newLineIndex = 0;
			while((newLineIndex = IndexOfNewLine(newLineIndex)) != -1)
			{
				string line = Encoding.UTF8.GetString(_buffer, lastNewLineIndex, newLineIndex - lastNewLineIndex - 2);
				OnLineRead(line);
				lastNewLineIndex = newLineIndex;
			}

			if(lastNewLineIndex > 0)
			{
				// Shifts the unhandled data in the buffer to the beginning.
				if(lastNewLineIndex < _bufferPosition)
					Buffer.BlockCopy(_buffer, lastNewLineIndex, _buffer, 0, _bufferPosition - lastNewLineIndex);
				_bufferPosition = 0;
			}
		}

		/// <summary>
		/// Returns index of character following the first cr+lf characters.
		/// </summary>
		/// <param name="startIndex"></param>
		/// <returns></returns>
		private int IndexOfNewLine(int startIndex)
		{
			bool crFound = false;
			for(int i = startIndex; i < _bufferPosition; i++)
			{
				if(_buffer[i] == 13)
					crFound = true;
				else if(_buffer[i] == 10 && crFound)
					return i + 1;
				else
					crFound = false;
			}
			return -1;
		}

		private void OnLineRead(string line)
		{
			if(this.LineRead != null)
			{
				try
				{
					this.LineRead(this, line);
				}
				catch(Exception e)
				{
					Log.Exception(ZoomPlayerApplication.TraceSource, e);
				}
			}
		}

		public event ReadLineEventHandler LineRead;
	}
}
